package com.ckzp.jfinal.base;

import com.ckzp.core.json.Jmode;
import com.ckzp.core.json.JsonResult;
import com.ckzp.core.json.JsonUtil;
import com.ckzp.core.model.Datas;
import com.ckzp.core.util.StringUtil;
import com.ckzp.jfinal.base.csrf.TokenService;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.core.Controller;
import com.jfinal.kit.LogKit;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.validate.ValidateException;

import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by 51594 on 2022/9/4.
 */
public abstract class Validator implements Interceptor {

    //controller
    protected Controller controller;
    //invocation
    protected Invocation invocation;
    //仅一次失败就反回，不执行下面的操作
    protected boolean shortCircuit = true;
    //是否有失败信息
    protected boolean invalid = false;
    //日期格式化
    protected String datePattern = null;
    //错误信息记录表
    protected List<Jmode> errors = new ArrayList();

    protected Datas datas = null;

    // TODO set the DEFAULT_DATE_PATTERN in Const and config it in Constants. TypeConverter do the same thing.
    protected static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd";
    protected static final String emailAddressPattern = "\\b(^['_A-Za-z0-9-]+(\\.['_A-Za-z0-9-]+)*@([A-Za-z0-9-])+(\\.[A-Za-z0-9-]+)*((\\.[A-Za-z0-9]{2,})|(\\.[A-Za-z0-9]{2,}\\.[A-Za-z0-9]{2,}))$)\\b";


    final public void intercept(Invocation invocation) {
        Validator validator = null;
        try {
            validator = getClass().newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        validator.controller = invocation.getController();
        validator.invocation = invocation;

        try {
            validator.validate(validator.controller);
        } catch (ValidateException e) {
            // should not be throw, short circuit validate need this
            LogKit.logNothing(e);
        }

        if (validator.invalid) {
            validator.handleError(validator.controller);
        } else {
            invocation.invoke();
        }
    }


    /**
     * Return the controller of this action.
     */
    protected Controller getController() {
        return controller;
    }

    /**
     * Return the action key of this action.
     */
    protected String getActionKey() {
        return invocation.getActionKey();
    }

    /**
     * Return the controller key of this action.
     */
    protected String getControllerKey() {
        return invocation.getControllerKey();
    }

    /**
     * Return the method of this action.
     */
    protected Method getActionMethod() {
        return invocation.getMethod();
    }

    /**
     * Return the method name of this action.
     */
    protected String getActionMethodName() {
        return invocation.getMethodName();
    }

    /**
     * Return view path of this controller.
     */
    protected String getViewPath() {
        return invocation.getViewPath();
    }


    /**
     * Use validateXxx method to validate the parameters of this action.
     */
    protected abstract void validate(Controller c);

    /**
     * Handle the validate error.
     * Example:<br>
     * controller.keepPara();<br>
     * controller.render("register.html");
     */
    // protected abstract void handleError(Controller c);

    /**
     * 设置短路验证. 默认值为 true
     * 短路验证是指在验证过程中，只要碰到验证失败则立即停止后续验证并返回
     * 非短路验证是指验证操作一直持续到结束，无论中途有没有碰到验证失败
     *
     * @param shortCircuit true 表示短路型验证
     */
    protected void setShortCircuit(boolean shortCircuit) {
        this.shortCircuit = shortCircuit;
    }

    /**
     * 取得验证表数据
     * 1从postdata取
     * 2从json中最
     * 兼容两种传入数据方式
     *
     * @param controller
     */
    private void setDatas(Controller controller) {
        Datas d = JsonUtil.json2Datas(controller.getPara("postdata"));
        if (d == null || d.isEmpty()) {
            d = JsonUtil.json2Datas(controller.getRawData());
        }
        if (d != null) datas = d;
    }

    protected boolean isEdit() {
        if (datas == null) setDatas(controller);
        return "EDIT".equals(datas.getStr("actions"));
    }

    /**
     * Add message when validate failure.
     */
    protected void addError(String errorKey, String errorMessage) {
        invalid = true;
        errors.add(Jmode.n().set("field", errorKey).set("error", errorMessage));
        if (shortCircuit) {
            throw new ValidateException();
        }
    }


    /**
     * 验证字符不能为空
     * Validate required string.
     */
    protected void validateRequiredString(String field, String errorMessage) {
        if (datas == null) setDatas(controller);
        if (StrKit.isBlank(datas.getStr(field))) {
            addError(field, errorMessage);
        }
    }

    /**
     * Validate Required. Allow space characters.
     */
    protected void validateRequired(String field, String errorKey, String errorMessage) {
        if (datas == null) setDatas(controller);
        Object value = datas.get(field);
        if (value == null || "".equals(value)) {    // 经测试,form表单域无输入时值为"",跳格键值为"\t",输入空格则为空格" "
            addError(errorKey, errorMessage);
        }
    }

    /**
     * 验证字符长府介于 slenth，llenth之间
     * Validate required string.
     */
    protected void validateRequiredLength(String field, int slenth, int llenth, String errorMessage) {
        if (datas == null) setDatas(controller);
        if ((datas.getStr(field)).length() < slenth || datas.getStr(field).length() > llenth) {
            errorMessage = StringUtil.replace(errorMessage, "{1}", String.valueOf(slenth));
            errorMessage = StringUtil.replace(errorMessage, "{2}", String.valueOf(llenth));
            addError(field, errorMessage);
        }
    }

    /**
     * @param field
     * @param minLen
     * @param maxLen
     * @param errorMessage
     */
    private void validateStringValue(String field, int minLen, int maxLen, String errorMessage) {
        if (datas == null) setDatas(controller);
        String value = datas.getStr(field);
        if (minLen > 0 && StrKit.isBlank(value)) {
            addError(field, errorMessage);
            return;
        }
        if (value == null) {        // 支持 minLen <= 0 且 value == null 的情况
            value = "";
        }
        if (value.length() < minLen || value.length() > maxLen) {
            addError(field, errorMessage);
        }
    }


    /**
     * Validate integer.
     */
    protected void validateInteger(String field, int min, int max, String errorMessage) {
        if (datas == null) setDatas(controller);
        String value = datas.getStr(field);
        validateIntegerValue(field, value, min, max, errorMessage);
    }


    /**
     * 判断 为 min,max之间的数字
     *
     * @param value
     * @param min
     * @param max
     * @param errorMessage
     */
    protected void validateIntegerValue(String field, String value, int min, int max, String errorMessage) {
        if (StrKit.isBlank(value)) {
            addError(field, errorMessage);
            return;
        }
        try {
            int temp = Integer.parseInt(value.trim());
            if (temp < min || temp > max) {
                errorMessage = StringUtil.replace(errorMessage, "{1}", String.valueOf(min));
                errorMessage = StringUtil.replace(errorMessage, "{2}", String.valueOf(max));
                addError(field, errorMessage);
            }
        } catch (Exception e) {
            errorMessage = StringUtil.replace(errorMessage, "{1}", String.valueOf(min));
            errorMessage = StringUtil.replace(errorMessage, "{2}", String.valueOf(max));
            addError(field, errorMessage);
        }
    }

    /**
     * 验证是否为数字
     *
     * @param field
     * @param errorMessage
     */
    protected void validateIntegerValue(String field, String errorMessage) {
        if (datas == null) setDatas(controller);
        String value = datas.getStr(field);
        if (StrKit.isBlank(value)) {
            addError(field, errorMessage);
            return;
        }
        try {
            Integer.parseInt(value.trim());
        } catch (Exception e) {
            addError(field, errorMessage);
        }
    }


    protected String getDatePattern() {
        return (datePattern != null ? datePattern : DEFAULT_DATE_PATTERN);
    }

    /**
     * Validate date. Date formate: yyyy-MM-dd
     */
    protected void validateDate(String field, String errorMessage) {
        if (datas == null) setDatas(controller);
        String value = datas.getStr(field);
        if (StrKit.isBlank(value)) {
            addError(field, errorMessage);
            return;
        }
        try {
            new SimpleDateFormat(getDatePattern()).parse(value.trim());    // Date temp = Date.valueOf(value); 为了兼容 64位 JDK
        } catch (Exception e) {
            addError(field, errorMessage);
        }
    }

    /**
     * Validate date.
     */
    protected void validateDate(String field, Date min, Date max, String errorMessage) {
        if (datas == null) setDatas(controller);
        String value = datas.getStr(field);
        if (StrKit.isBlank(value)) {
            addError(field, errorMessage);
            return;
        }
        try {
            Date temp = new SimpleDateFormat(getDatePattern()).parse(value.trim());    // Date temp = Date.valueOf(value); 为了兼容 64位 JDK
            if (temp.before(min) || temp.after(max)) {
                addError(field, errorMessage);
            }
        } catch (Exception e) {
            addError(field, errorMessage);
        }
    }

    /**
     * Validate date. Date formate: yyyy-MM-dd
     */
    protected void validateDate(String field, String min, String max, String errorMessage) {
        if (datas == null) setDatas(controller);
        String value = datas.getStr(field);
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(getDatePattern());
            validateDate(field, sdf.parse(min.trim()), sdf.parse(max.trim()), errorMessage);
        } catch (Exception e) {
            addError(field, errorMessage);
        }
    }

    /**
     * 验证表单内容是否存在
     * 不包括当前记录
     * Validate required string.
     */
    protected void validateFieldExit(String table, String key, String field, String errorMessage) {
        if (datas == null) setDatas(controller);
        try {
            Record record = Db.findFirst("select * from " + table + " where " + key + " <> ? and " + field + " =?", datas.get(key), datas.get(field));
            if (record != null) {
                addError(field, errorMessage);
            }
        } catch (Exception e) {
            addError(field, errorMessage);
        }
    }

    /**
     * 验证表单内容是否存在
     * 不包括当前记录
     * Validate required string.
     */
    protected void validateFieldExit(String table, String field, String errorMessage) {
        if (datas == null) setDatas(controller);
        try {
            Record record = Db.findFirst("select * from " + table + " where " + field + " =?", datas.get(field));
            if (record != null) {
                errorMessage = StringUtil.replace(errorMessage, "{1}", String.valueOf("[" + datas.get(field) + "]"));
                addError(field, errorMessage);
            }
        } catch (Exception e) {
            addError(field, errorMessage);
        }
    }

    /**
     * Validate equal string.
     * 判断对象是否相同
     */
    protected void validateEqualString(String field1, String field2, String errorMessage) {
        if (datas == null) setDatas(controller);
        Object value1 = datas.get(field1);
        Object value2 = datas.get(field2);
        if (value1 == null || value2 == null || (!value1.equals(value2))) {
            addError(field1, errorMessage);
        }
    }

    /**
     * 判断输入是否为手机号
     * 目前只判断了是否为空和长度是否为11
     */
    protected void validatePhoneNumber(String field, String errorMessage) {
        if (datas == null) setDatas(controller);
        String phone = datas.getStr(field);
        if (phone == null || phone.length() != 11) {
            addError(field, errorMessage);
        }
    }

    /**
     * 判断输入是否为数字
     * 目前只判断了是否为数字
     */
    protected void validateNumber(String field, String errorMessage) {
        validateIntegerValue(field,errorMessage);
    }

    /**
     * Validate email.
     */
    protected void validateEmail(String field, String errorMessage) {
        validateRegex(field, emailAddressPattern, false, errorMessage);
    }

    /**
     * Validate URL.
     */
    protected void validateUrl(String field, String errorKey, String errorMessage) {
        String value = controller.getPara(field);
        if (StrKit.isBlank(value)) {
            addError(errorKey, errorMessage);
            return;
        }
        try {
            value = value.trim();
            if (value.startsWith("https://")) {
                value = "http://" + value.substring(8); // URL doesn't understand the https protocol, hack it
            }
            new URL(value);
        } catch (MalformedURLException e) {
            addError(errorKey, errorMessage);
        }
    }

    /**
     * Validate regular expression.
     */
    protected void validateRegex(String field, String regExpression, boolean isCaseSensitive, String errorMessage) {
        String value = controller.getPara(field);
        if (value == null) {
            addError(field, errorMessage);
            return;
        }
        Pattern pattern = isCaseSensitive ? Pattern.compile(regExpression) : Pattern.compile(regExpression, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(value);
        if (!matcher.matches()) {
            addError(field, errorMessage);
        }
    }

    /**
     * Validate regular expression and case sensitive.
     */
    protected void validateRegex(String field, String regExpression, String errorKey, String errorMessage) {
        validateRegex(field, regExpression, true, errorMessage);
    }

    /**
     * Validate string.
     */
    protected void validateString(String field, int minLen, int maxLen, String errorMessage) {
        validateStringValue(controller.getPara(field), minLen, maxLen, errorMessage);
    }


    /**
     * get error info
     *
     * @return
     */
    protected Jmode getError() {
        StringBuffer keys = new StringBuffer();
        StringBuffer value = new StringBuffer();
        Jmode jmode = Jmode.n();
        for (Jmode m : errors) {
            if (keys.length() == 0 || !shortCircuit) {
                keys.append("#" + m.getStr("field") + ",");
                value.append(m.getStr("error") + ";");
            }
        }
        jmode.put("fields", StringUtil.delEnd(keys.toString(), ","));
        jmode.put("errors", StringUtil.delEnd(value.toString(), ";"));
        new TokenService().createToken(controller);
        jmode.put("token_name", controller.getAttr("token_name"));
        jmode.put("token_value", controller.getAttr("token_value"));
        return jmode;
    }

    /**
     * 验证失败处理类
     * 如果没有失败信息，不会调用本方法
     *
     * @param controller
     */
    protected void handleError(Controller controller) {
        controller.renderJson(new JsonResult(999).setMsg("校验失败").setData(getError()));
    }
}
